Python之路 - 进程与线程
前言 🍀
进程与线程是操作系统中的概念 , 这也是操作系统中最核心的概念
进程 🍀
进程是对正在运行程序的一个抽象 , 即一个进程就是一个正在执行程序的实例
从概念上说 , 每个进程拥有它自己的虚拟CPU . 当然 , 实际上真正的CPU在各进程之间来回切换 . 这种快速切换就是多道程序设计 . 但是某一瞬间 , CPU只能运行一个进程 , 但在1秒钟期间 , 它可能运行多个进程 , 就是CPU在进行快速切换 , 有时人们所说的 伪并行
就是指这种情形
创建进程 🍀
操作系统中 , 有4种事件导致进程的创建
- 系统初始化 , 启动操作系统时 , 通常会创建若干个进程 , 分为前台进程和后台进程
- 执行了正在运行的进程所调用的进程创建系统调用
- 用户请求创建一个新的进程
- 一个批处理作业的初始化
从技术上看 , 在所有这些情况中 , 新进程都是由一个已存在的进程执行了一个用于创建进程的系统调用而创建的 . 这个进程可以是一个运行的用户进程 , 一个由键盘或鼠标启动的系统进程或者一个批处理管理进程 . 这个进程所做的工作是 , 执行一个用来创建新进程的系统调用 . 在Linux/Unix中提供了一个fork()
系统调用就是用来创建进程的 (子进程) , 当然在Windows中也有相对应的系统调用
在Python中的os模块封装了常见的系统调用 , 其中就包括fork , 可以在Python程序中轻松创建子进程
1 | '''因为Windows中没有fork调用,所以下程序只能在Unix/Linux下执行''' |
终止进程 🍀
进程不可能永恒的存在 , 迟早都会终止 , 通常由下列条件引起 :
- 正常退出(自愿的) , 任务完成退出
- 出错退出(自愿的) , 进程中的错误
- 严重错误(非自愿) , 由进程引起的错误
- 被其他进程杀死(非自愿) , 某进程执行一个系统调用通知操作系统杀死某个其他进程
在有些系统中 , 当一个进程终止时 , 不论是自愿的还是其他原因 , 由该进程所创建的所有进程也一律立即被杀死 . 不过Unix和Windows都不是这种工作方式
进程状态 🍀
每个进程都有自己的程序计数器和内部状态 , 但进程之间经常需要相互作用 , 一个进程的输出结构可能作为另一个进程的输入 , 所以进程就会出现如下三种状态 :
- 运行态(该时刻进程实际占用CPU)
- 就绪态(可运行 , 但因为其他进程正在运行而暂时停止)
- 阻塞态(除非某中外部事件发生 , 否则进程不能运行)
进程的三种状态之间有四种可能的转换关系
一个进程状态 | 另一个进程状态 | 过程 |
---|---|---|
运行态 | 阻塞态 | 进程为等待输入而 |
运行态 | 就绪态 | 调度程序选择另一个进程 |
就绪态 | 运行态 | 调度程序选择这个进程 |
阻塞态 | 就绪态 | 出现有效输入 |
进程中还有一点就是进程实现的问题 , 这就是依靠进程表了 , 具体就不说明了
进程的作用主要是提供了多道编程(多进程) , 并且提高了计算机的利用率 , 但是有两点是进程没有解决的 :
- 进程在同一时间只能做一件事 , 显然这不够我们的需求
- 进程在执行过程中一旦阻塞 , 整个进程就挂起了 , 这也是对计算机资源的一种浪费
人们想到的解决办法就是 , 在一个进程里面再有一类进程 , 称为迷你进程 , 也就是下面要说的线程
线程 🍀
在传统操作系统中 , 每个进程有一个地址空间和一个控制线程 , 事实上 , 这几乎就是进程的定义
所以我们可以知道 , 线程是操作系统能够进程运算调度的最小单位 , 它被包含在进程之中 , 是进程中的实际运作单位 . 不过 , 经常存在在同一个地址空间中准并行运行多个控制线程的情况 , 这些线程就像分离的进程
一个线程指的是进程中一个单一顺序的控制流 , 一个进程中可以并发多个线程
线程的使用 🍀
人们需要使用线程有两个理由 :
- 在多进程模型中 , 没有并行实体共享同一个地址空间和所有可用数据的能力
- 线程比进程更轻量级 , 在许多系统中 , 创建一个线程较创建一个进程要快10~100倍
线程与进程的区别 🍀
- 线程是执行的指令集 , 进程是资源的集合
- 线程的启动速度要比进程的启动速度要快
- 两个线程的执行速度是一样的
- 进程与线程的运行速度是没有可比性的
- 线程共享创建它的进程的内存空间 , 进程的内存是独立的
- 两个线程共享的数据都是同一份数据 , 两个子进程的数据不是共享的 , 而且数据是独立的
- 同一个进程的线程之间可以直接交流 , 同一个主进程的多个子进程之间是不可以进行交流 , 如果两个进程之间需要通信 , 就必须要通过一个中间代理来实现
- 一个新的线程很容易被创建 , 一个新的进程创建需要对父进程进行一次克隆
- 一个线程可以控制和操作同一个进程里的其他线程 , 线程与线程之间没有隶属关系 , 但是进程只能操作子进程
- 改变主线程 , 有可能会影响到其他线程的行为 , 但是对于父进程的修改是不会影响子进程
并发与并行 🍀
###并发 🍀
在早期操作系统只有一个处理器 , 所以想达到同时运行多个程序 , 显然是不可能的 , 唯一的办法就是骗自己 , 告诉自己这几个是”同时”在运行 , 怎么骗 ? 如下
🌰一
1 | 现在你女朋友要你同时做三件事 |
这就是操作系统中 , 单个CPU + 多道技术实现的并发
CPU就是你本人 , 多道技术就是我赋予你的用速度”同时”干多件事的能力
🌰二
1 | 现在你女朋友已经知道你有超能力了,原来你一下只能干一件事情,她不高兴了,说道:我不管你得同时陪我还得做事情 |
这就是分时系统的并发 , 按时间进行分配
并发 , 就是伪并行的
并行 🍀
真正的同时运行 , 只有具备多个CPU才能实现
并发事实上就是串行 , 还是一个人在做多个任务 , 而并行则是多个人在做多个任务 . 明显一个人 , 即只有一个执行者同时不可能做两件事的 , 但是并行 , 多个执行者就能够同时做多件事
所以并发与并行 , 就是一瞬间是否能存在多个进程
同步与异步 🍀
同步 🍀
所谓同步 , 就是在发出一个功能调用时 , 在没有得到结果之前,该调用就不会返回 . 按照这个定义,其实绝大多数函数都是同步调用 . 但是一般而言 , 我们在说同步、异步的时候 , 特指那些需要其他部件协作或者需要一定时间完成的任务
异步 🍀
异步的概念和同步相对 , 当一个异步功能调用发出后 , 调用者不能立刻得到结果 . 当该异步功能完成后 , 通过状态、通知或回调来通知调用者 , 如果异步功能用状态来通知 , 那么调用者就需要每隔一定时间检查一次 , 效率就很低(有些初学多线程编程的人 , 总喜欢用一个循环去检查某个变量的值 , 这其实是一 种很严重的错误) . 如果是使用通知的方式 , 效率则很高 , 因为异步功能几乎不需要做额外的操作 . 至于回调函数 , 其实和通知没太多区别
阻塞与非阻塞 🍀
阻塞 🍀
阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已
非阻塞 🍀
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程
小结 🍀
- 对于进程和线程 , 直接阅读《现代操作系统》 一书再好不过了
- 并发与并行要注意执行顺序的问题
- 同步与异步针对的是函数/任务的调用方式 , 是否等待结果
- 阻塞与非阻塞针对的是进程或线程 , 阻塞进程则挂起 , 非阻塞即不挂起
这一篇基本属于纯理论 , 罗哩罗嗦了半天